#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <unistd.h>

#include "r_socket_domain_udp_server.h"
#include "r_socket_common.h"
#include "r_socket_obj_manager.h"

#include "utilities/string_operator.h"
#include "utilities/r_logger.h"

namespace eniac
{

r_socket_proxy_ret_t r_socket_domain_udp_server::set_socket_property(const r_socket::ptr socket_obj, r_socket_property_type type, void* property, size_t property_size)
{
    LOG_DBG() << "r_socket_domain_udp_server::set_socket_property entry";

    if (nullptr == socket_obj.get())
    {
        return r_socket_proxy_ret_t::NULL_PTR;
    }

    std::lock_guard<std::mutex> lg{socket_obj->m_mutex};

    if (type == r_socket_property_type::SOCKET_PROPERTY_OWN_ADDRESS)
    {
        return set_socket_own_address(socket_obj, property, property_size);
    }

    return r_socket_proxy_ret_t::INVALID_PROPERTY_TYPE_FOR_THE_SOCKET;
}

r_socket_proxy_ret_t r_socket_domain_udp_server::start_socket(const r_socket::ptr socket_obj)
{
    if (nullptr == socket_obj.get())
    {
        return r_socket_proxy_ret_t::NULL_PTR;
    }

    std::lock_guard<std::mutex> lg{socket_obj->m_mutex};

    if (socket_obj->m_own_address.empty())
    {
        return r_socket_proxy_ret_t::OWN_ADDRESS_HAS_NOT_BEEN_SET_YET;
    }

    if (socket_obj->m_socket_fd != INVALID_SOCKET_FD)
    {
        return r_socket_proxy_ret_t::SOCKET_FD_HAS_BEEN_SET;
    }

    if ((socket_obj->m_socket_fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
    {
        return r_socket_proxy_ret_t::TO_CREATE_SOCKET_FD_IS_FAILED;
    }

    sockaddr_un server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = AF_UNIX;
    strncpy(&server_addr.sun_path[1], socket_obj->m_own_address.data(), socket_obj->m_own_address.length());
    int bind_ret = bind(socket_obj->m_socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if (bind_ret < 0)
    {
        LOG_DBG() << "Bind Ret is " << bind_ret << " errno " <<  errno <<  "  strerror(errno) " << strerror(errno);
        close(socket_obj->m_socket_fd);
        socket_obj->m_socket_fd = INVALID_SOCKET_FD;
        return r_socket_proxy_ret_t::TO_BIND_ADDRESS_TO_FD_IS_FAILED;
    }

    socket_obj->m_status = r_socket_status_t::R_SOCKET_STATUS_STARTED;
    r_socket_obj_manager::get_instance()->update_socket_obj(socket_obj);
    return r_socket_proxy_ret_t::OK;
}

r_socket_proxy_ret_t r_socket_domain_udp_server::read_message(const r_socket::ptr socket_obj, r_socket_message::ptr& message)
{
    if (nullptr == socket_obj.get())
    {
        return r_socket_proxy_ret_t::NULL_PTR;
    }

    std::lock_guard<std::mutex> lg{socket_obj->m_mutex};

    if (socket_obj->m_socket_fd == INVALID_SOCKET_FD)
    {
        return r_socket_proxy_ret_t::INVALID_SOCKET_FD;
    }

    uint64_t readable_bytes = r_socket_common::get_readable_bytes_length(socket_obj->m_socket_fd);
    if (0 == readable_bytes)
    {
        return r_socket_proxy_ret_t::NO_DATA_CAN_BE_READ;
    }

    message = r_socket_message::create_obj();
    message->m_payload.resize(readable_bytes);

    struct sockaddr_un client_addr;
    memset(&client_addr, 0, sizeof(client_addr));
    socklen_t client_addr_len = sizeof(client_addr);
    ssize_t valread = recvfrom(socket_obj->m_socket_fd, message->m_payload.data(), message->m_payload.size(), 0, (struct sockaddr*)&client_addr, &client_addr_len);
    if (valread <= 0)
    {
        message.reset();
        return r_socket_proxy_ret_t::TO_READ_DATA_FROME_SOCKET_IS_FAILED;
    }

    LOG_DBG() << "Receive Client address is " << &client_addr.sun_path[1];

    r_socket::ptr partner_obj = std::make_shared<r_socket>();
    partner_obj->m_own_address = &client_addr.sun_path[1];
    partner_obj->m_own_port = 0;
    partner_obj = r_socket_obj_manager::get_instance()->is_partner_exists(socket_obj, partner_obj);
    if (nullptr == partner_obj.get())
    {
        partner_obj = r_socket_obj_manager::get_instance()->create_socket_obj(r_socket_type::SOCKET_TYPE_DOMAIN_UDP_PARTNER);
        LOG_DBG() << "This client is first received. id is " << partner_obj->m_socket_id;
        if (nullptr != partner_obj.get())
        {
            partner_obj->m_status = r_socket_status_t::R_SOCKET_STATUS_STARTED;
            partner_obj->m_own_address = &client_addr.sun_path[1];
            partner_obj->m_own_port = INVALID_SOCKET_PORT;
            partner_obj->m_host_id = socket_obj->m_socket_id;
            r_socket_obj_manager::get_instance()->update_socket_obj(partner_obj);
            socket_obj->m_parnters.insert(partner_obj->m_socket_id);
            message->m_socket_id = partner_obj->m_socket_id;
            LOG_INF() << "New Domain partner is " << partner_obj->m_own_address;
        }
    }
    else
    {
        LOG_DBG() << "This client is not first received. id is " << partner_obj->m_socket_id;
        message->m_socket_id = partner_obj->m_socket_id;
    }

    return r_socket_proxy_ret_t::OK; 
}

void r_socket_domain_udp_server::close_socket(const r_socket::ptr socket_obj)
{
    if (nullptr == socket_obj.get())
    {
        return;
    }

    std::lock_guard<std::mutex> lg{socket_obj->m_mutex};
    if (socket_obj->m_socket_fd == INVALID_SOCKET_FD)
    {
        return;
    }

    for (const auto& partner_id : socket_obj->m_parnters)
    {
        r_socket::ptr partner =  r_socket_obj_manager::get_instance()->get_socket_obj_by_id(partner_id);
        if (nullptr == partner.get())
        {
            continue;
        }
        if (INVALID_SOCKET_FD != partner->m_socket_fd)
        {
            close(partner->m_socket_fd);
            partner->m_socket_fd = INVALID_SOCKET_FD;
        }
        r_socket_obj_manager::get_instance()->remove_socket_obj(partner->m_socket_id);
    }
    socket_obj->m_parnters.clear();

    close(socket_obj->m_socket_fd);
    socket_obj->m_socket_fd = INVALID_SOCKET_FD;
    socket_obj->m_status = r_socket_status_t::R_SOCKET_STATUS_STOPPING;
    r_socket_obj_manager::get_instance()->update_socket_obj(socket_obj);
}

r_socket_proxy_ret_t r_socket_domain_udp_server::set_socket_own_address(const r_socket::ptr socket, void* property, size_t property_size)
{
    LOG_DBG() << "r_socket_domain_udp_server::set_socket_own_address entry";

    if ( 0 >= property_size  ||
         property_size > 107 )
    {
        return r_socket_proxy_ret_t::INVALID_PROPERTY_LENGTH;
    }

    const char* c_addr = reinterpret_cast<const char*>(property);
    if (strlen(c_addr) != property_size)
    {
        return r_socket_proxy_ret_t::INVALID_PROPERTY_VALUE;
    }

    socket->m_own_address = c_addr;

    return r_socket_proxy_ret_t::OK;
}

}